package com.zhxy.controller.api.interceptor;

import java.io.IOException;
import java.util.Calendar;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.web.servlet.HandlerMapping;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;

import com.zhxy.business.ApiLimitManager;
import com.zhxy.domain.ApiLimit;
import com.zhxy.domain.ApiLimitExample;
import com.zhxy.util.IPUtil;
import com.zhxy.util.RedisUtils;

/**
 * 流量监控拦截器
 * 使用redis作为存储，记录每个API的流量使用情况
 * 
 * @author xinyue.zheng
 */
public class FlowLimitInterceptor extends HandlerInterceptorAdapter {

    private static final Logger logger = LoggerFactory.getLogger(FlowLimitInterceptor.class);

    private List<String> specialUris;// 需要做特殊处理的地址
    @Autowired
    private ApiLimitManager apiLimitManger;

    @Override
    public boolean preHandle(HttpServletRequest req, HttpServletResponse res, Object handler) throws IOException {
        /*
         * 规则：
         * 取限定量：根据url为key，从redis中获取value1
         * 获取流量：根据用户id,url,日期,ip为key，从redis中获取value2
         * 
         * 假如value1为null => 需要从数据库里获取数据，并保存到redis中。
         * 为0 => 流量已经超。
         * 假如value2为null => 初始化为1。
         * 为非空 => 判断value1与value2的大小关系
         * value1 > value2+1 => 未超出流量
         * value1 = value2+1 => 未超出流量
         * value1 < value2+1 => 超出流量
         * 超出流量直接返回false，并且有输出错误信息
         * 未超出流量需要更新redis里的用量，并且返回true
         * 
         */
        // 获取限定量
        String api = (String) req.getAttribute(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE);
        Character lastChar = api.charAt(api.length() - 1);
        if (lastChar == '/' || lastChar == '\\') {
            api = api.substring(0, api.length() - 1);
        }
        String userid = getUseridFromRequest(req);
        String limitStr = RedisUtils.get(api);
        Integer limit;
        Integer index;
        if (limitStr == null) {
            // 从mysql中获取api对应的限定量
            ApiLimitExample example = new ApiLimitExample();
            example.createCriteria().andApi_urlEqualTo(api);
            List<ApiLimit> list = apiLimitManger.selectByExample(example);
            if (list.isEmpty()) {
                logger.info("数据库没有该api的限定量. 通过拦截.");
                return true;
            } else {
                limit = list.get(0).getLimit_count();
                index = list.get(0).getId();
            }
            // 保存在redis中
            RedisUtils.save(api, limit.toString());
            RedisUtils.save(api + "_index", index.toString());
        } else if ("0".equals(limitStr)) {
            // 流量已超
            logger.error("流量已超,api的限定量是0,");
            return false;
        } else {
            limit = Integer.valueOf(limitStr);
        }

        Calendar now = Calendar.getInstance();
        Long ipLong = IPUtil.ipToLong(IPUtil.getRemoteHost(req));
        String indexStr = RedisUtils.get(api + "_index");
        String usedKey = now.get(Calendar.DAY_OF_MONTH) + "_" + userid + "_" + indexStr + "_" + ipLong;
        String lockKey = usedKey + "_lock";
        String lockValue = null;
        try {
            lockValue = RedisUtils.lock(lockKey, 2000L);
            String usedFlowStr = RedisUtils.get(usedKey);
            if (usedFlowStr != null) {
                Integer usedFlow = Integer.valueOf(usedFlowStr) + 1;
                if (limit >= usedFlow) {
                    // 更新redis，返回true
                    RedisUtils.save(usedKey, usedFlow.toString());
                    return true;
                } else {
                    logger.error("api的调用已经超过限定,无法继续调用");
                    // 输出错误信息
                    res.sendError(HttpStatus.BAD_REQUEST.value(), "已经超过限定，无法继续调用.");
                    return false;
                }
            } else {
                RedisUtils.save(usedKey, "1");
                return true;
            }
        } finally {
            unlockJedis(lockKey, lockValue);
        }
    }

    public List<String> getSpecialUris() {
        return specialUris;
    }

    public void setSpecialUris(List<String> specialUris) {
        this.specialUris = specialUris;
    }

    public static String getRandomString(int length) { // length表示生成字符串的长度
        String base = "abcdefghijklmnopqrstuvwxyz0123456789";
        Random random = new Random();
        StringBuffer sb = new StringBuffer();
        for (int i = 0; i < length; i++) {
            int number = random.nextInt(base.length());
            sb.append(base.charAt(number));
        }
        return sb.toString();
    }

    /**
     * 根据请求获取url路径中的userid
     * @param request
     * @return
     */
    @SuppressWarnings("unchecked")
    private String getUseridFromRequest(HttpServletRequest request) {
        Map<String, Object> params = (HashMap<String, Object>) request
                .getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE);
        String userId = (String) params.get("userid");
        if (StringUtils.isBlank(userId)) {
            userId = (String) params.get("userId");
        }
        return userId;
    }

    /**
     * 解锁
     * @param lockKey
     * @param lockValue
     */
    private void unlockJedis(String lockKey, String lockValue) {
        try {
            if (StringUtils.isNotBlank(lockKey) && StringUtils.isNotBlank(lockValue)) {
                RedisUtils.unlock(lockKey, lockValue);
            }
        } catch (Exception e) {
            logger.error("error when unlockJedis,lockKey is {}.{}.", lockKey, e);
        }
    }
}
